/*
 * Filename OrderService.java 2022年03月03日
 * Copyright © Ehome Co.Ltd. All Rgiths Reserved.
 * @author wuyingchao
 */
package com.ehome.poct.terminal.service;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.ehome.exception.BizCheckedException;
import com.ehome.exception.BizException;
import com.ehome.poct.base.api.dto.doctor.ClinicDTO;
import com.ehome.poct.base.api.enums.StripTypeEnums;
import com.ehome.poct.base.api.feign.ClinicClient;
import com.ehome.poct.base.api.feign.DeviceClient;
import com.ehome.poct.base.api.qry.device.DeviceOneQry;
import com.ehome.poct.core.api.constant.enums.CheckMessageTypeEnums;
import com.ehome.poct.core.api.constant.enums.PayTypeEnum;
import com.ehome.poct.core.api.dto.check.ProjectNameReportDTO;
import com.ehome.poct.core.api.dto.check.TestRecordDetailDTO;
import com.ehome.poct.core.api.dto.order.OrderDTO;
import com.ehome.poct.core.api.dto.order.TestOrderCheckRecordDTO;
import com.ehome.poct.core.api.dto.order.TestOrderDTO;
import com.ehome.poct.core.api.dto.report.ProfitDTO;
import com.ehome.poct.core.api.feign.*;
import com.ehome.poct.core.api.qry.OrderQry;
import com.ehome.poct.core.api.qry.StripPriceProfitQry;
import com.ehome.poct.core.api.qry.TestOrderQry;
import com.ehome.poct.core.api.qry.UniRecordQry;
import com.ehome.poct.core.api.vo.checkdata.TestRecordDetailVO;
import com.ehome.poct.core.api.vo.order.*;
import com.ehome.poct.terminal.api.constant.AppBizCode;
import com.ehome.poct.terminal.mapping.OrderMapping;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 订单服务
 *
 * @author wyc
 * @since 2022/03/03 14:52
 **/
@Service
@AllArgsConstructor
@Slf4j
public class OrderService {
    private final OrderClient orderClient;
    private final ClinicClient clinicClient;
    private final ReportClient reportClient;
    private final TestRecordDetailClient recordDetailClient;
    private final TestUniRecordClient uniRecordClient;
    private final DeviceClient deviceClient;
    private final PreOrderClient preOrderClient;
    private final OrderMapping orderMapping;
    private final JbyReportServiceImpl jbyReportService;
    private final PatientClient patientClient;
    private final OrderCheckRocordClient orderCheckRocordClient;

    private final TestStripPriceProfitSchemeFeignClient profitSchemeFeignClient;

    @Transactional(rollbackFor = Exception.class)
    public QrCodeVO place(AddOrderDTO dto) {
        final var clinic = clinicClient.getById(dto.getClinicId());
        final var orderNo = generateOrderNo(dto.getDeviceSno());

        //返回是否需要支付true:无需支付，false：需要支付
        final var isPay = this.isPay(clinic);

        // 1. 验证诊所的价格方案是否合法
        if (ObjectUtil.isNull(clinic.getTestStripPriceProfitSchemeId())) {
            throw new BizCheckedException(AppBizCode.PRICE_NOT_EXIST);
        }

        // 2. 生成检报告  接口里面会更新检测记录 这时候都没有插入数据  并没有什么卵用
        final var report = reportClient.createByDataId(dto.getDataIds().get(0), orderNo, isPay);
        // 4. 创建订单
        final var orderItem = orderMapping.toItem(report, new ProfitDTO(), orderNo);
        final var order = orderMapping.to(report, orderNo, BigDecimal.ZERO, CheckMessageTypeEnums.XCG.getType(), List.of(orderItem));
        order.setClinicId(clinic.getTermClinicId());

        // 这个接口里面会保存检查记录 test_record_detail
        QrCodeVO qrCodeVO = orderClient.saveWithItem(order, isPay);

        // 这边需要更新检测记录 上面的接口可能存在共用的情况，把更新放到下面来
        // 原逻辑是在这里重新插一条数据进来
        TestRecordDetailDTO recordDetailDTO = new TestRecordDetailDTO().setOrderNo(orderNo)
                .setStripTypeCode(StripTypeEnums.BLD.getStripTypeCode())
                .setTestTime(report.getCheckTime())
                .setTestReportId(report.getBloodCheckReportId())
                .setSno(dto.getDeviceSno());
        final TestRecordDetailVO recordDetailVO = recordDetailClient.updateRecordInfo(recordDetailDTO);
        log.info("update record detail response :[{}]", recordDetailVO);
        return qrCodeVO;
    }

    private boolean isPay(ClinicDTO clinic) {
        return Optional.ofNullable(clinic).map(it -> it.getPayType() >= PayTypeEnum.FREE_PAY.getType()).orElse(Boolean.TRUE);
    }

    public QrCodeVO unionPlace(AddOrderDTO dto) {
        final var clinic = clinicClient.getById(dto.getClinicId());
        final var unionRecord = uniRecordClient.getById(dto.getRecordId());

        //返回是否需要支付true:无需支付，false：需要支付
        final var isPay = this.isPay(clinic);
        // 联测是否完成
        final var isFinish = Optional.ofNullable(unionRecord).map(r -> StrUtil.isNotEmpty(r.getTestOrderNo()))
                .orElseThrow(() -> new BizCheckedException(AppBizCode.TODO_NOT_EXITS));
        // 订单号
        var orderNo = isFinish ? unionRecord.getTestOrderNo() : generateOrderNo(dto.getDeviceSno());

        // 1. 验证诊所的价格方案是否合法
        if (ObjectUtil.isNull(clinic.getTestStripPriceProfitSchemeId())) {
            throw new BizCheckedException(AppBizCode.PRICE_NOT_EXIST);
        }

        // 2. 生成检报告
        final var report = reportClient.createByDataId(dto.getDataIds().get(0), orderNo, isPay);

        // 3. 联测完成，获取支付链接
        if (isFinish) {
            // 3.1. 更改检测记录状态
            final var uniOnOrderDTO = orderMapping.to(orderClient.getOne(new OrderQry().setTestOrderNo(orderNo)));
            //判断是否需要支付
            if (isPay) {
                //修改金标仪报告状态
                jbyReportService.updateReportStatus(orderNo);
                //修改订单状态
                orderClient.updateOrderStatus(orderNo);
                return new QrCodeVO().setOrderNo(orderNo).setPayUrl("无需支付").setPromptMsg("无需支付");
            }
            // 3.2. 支付链接
            return orderClient.unionPlace(dto.getRecordId(), uniOnOrderDTO, dto.getDeviceType());
        }
        // 4. 联测未完成，预下单
        else {
            // 4.1. 价格方案
            //final var profit = priceProfitClient.getOne(new ProfitPlanQry(clinic.getTestStripPriceProfitSchemeId(), report.getProjectCode()));
            StripPriceProfitQry profitQry = new StripPriceProfitQry();
            profitQry.setClinicId(dto.getClinicId());
            profitQry.setStripTypeCodes(Lists.newArrayList(report.getProjectCode()));
            List<ProfitDTO> profitDTOList = profitSchemeFeignClient.getProfitService(profitQry);
            // todo check
            ProfitDTO profit = profitDTOList.get(0);
            // 4.2. 创建订单
            final var orderItem = orderMapping.toItem(report, profit, orderNo);
            // 先写死 三联测类型是3
            orderItem.setTestPoctReportType(3);
            final var order = orderMapping.to(report, orderNo, profit.getSalePrice(), 4, List.of(orderItem));
            // 4.3. 生成检测记录
            final var record = orderMapping.to(order, dto.getDeviceSno(), clinic, report.getBloodCheckReportId(), report.getProjectCode(), isPay, report.getCheckTime());
            final var jbyDevice = deviceClient.queryOne(new DeviceOneQry().setTermClinicId(dto.getClinicId()).setDeviceType(1));
            // 4.3.1 联合检测补充第二个设备的编号和人份数据
            Optional.ofNullable(jbyDevice).ifPresent(it -> record.setSno2(it.getSno()).setTestPeoplePortion2(BigDecimal.ONE));
            recordDetailClient.batchSave(List.of(record));
            // 4.4. 预下单
            orderClient.prePlace(dto.getRecordId(), order, dto.getDeviceType());
            return null;
        }
    }

    public QrCodeVO rePlace(String orderNo) {
        final var record = uniRecordClient.getOne(new UniRecordQry().setTestOrderNo(orderNo));
        final var checkIsFinish = Optional.ofNullable(record).map(r -> r.getJbyStatus().equals(10) && r.getXcgStatus().equals(10)).orElse(Boolean.TRUE);
        if (!checkIsFinish) {
            throw new BizException(AppBizCode.OL_CHECK_UNDO);
        }
        return orderClient.getPayLink(orderNo);
    }

    public PreOrderRspVO prePlace(AddPreOrderVO vo) {
        List<Integer> stripCodeList = convertTripleTestStripCode(vo.getStrips());
        final var names = vo.getStrips().stream().map(CheckStripVO::getName).collect(Collectors.joining("+"));
        final var orderNo = generateOrderNo("H" + vo.getClinicId());
        final var clinic = clinicClient.getById(vo.getClinicId());
        final var preOrder = orderMapping.to(vo, names, orderNo, stripCodeList, clinic.getTestStripPriceProfitSchemeId(), clinic.getTermAgentId());
        return preOrderClient.place(preOrder);
    }

    /**
     * 三联测stripsCode转换
     *
     * @param checkStripVOList
     * @return
     */
    private List<Integer> convertTripleTestStripCode(List<CheckStripVO> checkStripVOList) {
        final var stripsCodeList = checkStripVOList.stream().map(CheckStripVO::getCode).collect(Collectors.toList());
        if (!CollectionUtil.containsAll(stripsCodeList, StripTypeEnums.U_CRP_SAA_BLD_REPORT.getCombinationCodeList())) {
            return stripsCodeList;
        }
        stripsCodeList.removeAll(StripTypeEnums.U_CRP_SAA_BLD_REPORT.getCombinationCodeList());
        stripsCodeList.add(StripTypeEnums.U_CRP_SAA_BLD_REPORT.getStripTypeCode());

        rebuildCheckStripList(checkStripVOList);
        return stripsCodeList;
    }

    /**
     * 重新构建检测项
     *
     * @param checkStripVOList
     */
    private void rebuildCheckStripList(List<CheckStripVO> checkStripVOList) {
        // 过滤指定的项目
        List<CheckStripVO> collect = checkStripVOList.stream()
                .filter(checkStripVO -> !StripTypeEnums.U_CRP_SAA_BLD_REPORT.getCombinationCodeList().contains(checkStripVO.getCode()))
                .collect(Collectors.toList());

        checkStripVOList.clear();
        checkStripVOList.addAll(collect);
        //添加三联测code
        CheckStripVO checkStripVO = new CheckStripVO();
        checkStripVO.setCode(StripTypeEnums.U_CRP_SAA_BLD_REPORT.getStripTypeCode());
        checkStripVO.setName(StripTypeEnums.U_CRP_SAA_BLD_REPORT.getDesc());
        checkStripVOList.add(checkStripVO);
    }

    /**
     * 根据设备编号生成订单号
     * 规则：设备编号 + 当前时间（年份取后两位）+ 随机4位数字
     *
     * @param deviceSno 设备编号
     * @return 返回32位订单编号
     */
    private String generateOrderNo(String deviceSno) {
        return deviceSno + DateUtil.format(LocalDateTime.now(), "yyMMddHHmmss") + RandomUtil.randomNumbers(4);
    }

    public CheckOrderVO pageOrderList(OrderDTO orderDTO) {
        TestOrderQry qry = new TestOrderQry();
        qry.setPageSize(orderDTO.getPageSize());
        qry.setPageIndex(orderDTO.getPageIndex());
        qry.setClinicId(orderDTO.getClinicId());
        if (orderDTO.getStatus() != null) {
            if (orderDTO.getStatus() == 0) {
                //0：已检测待支付和 25：未检测待支付
                qry.setList(List.of(0, 25));
                qry.setStartTime(LocalDateTime.now().minusHours(24));
                qry.setEndTime(LocalDateTime.now());
            } else {
                qry.setList(List.of(orderDTO.getStatus()));
            }
        }
        var page = orderClient.pageOrderList(qry);
        var list = orderMapping.to(page);
        List<ProjectNameReportDTO> projectNameList = reportClient.projectNameList(list.getList().stream().map(OrderVO::getTestOrderNo).collect(Collectors.toList()));
        Map<String, List<String>> projectMap = projectNameList.stream().collect(Collectors.toMap(ProjectNameReportDTO::getOrderNo, ProjectNameReportDTO::getReportName));
        list.getList().forEach(r -> {
            List<String> projectName = projectMap.getOrDefault(r.getTestOrderNo(), new ArrayList<>());
            r.setTestItem(String.join("、", projectName));
            r.setName("伊鸿健康");
            r.setSex(1);
            if (r.getPatientId() != null) {
                var patient = patientClient.getByPatientId(r.getPatientId());
                if (Objects.isNull(patient)) {
                    return;
                }
                r.setName(patient.getName());
                r.setSex(patient.getSex());
                r.setAge(patient.getAge());
                r.setMonth(patient.getMonth());
            }
            //查找预约码
            var checkRecord = orderCheckRocordClient.getOne(new TestOrderCheckRecordDTO().setOrderNo(r.getTestOrderNo()));
            r.setReservationCode(checkRecord == null ? null : String.format("%04d", checkRecord.getReservationCode()));
            //判断未支付是否过期
            if ((r.getStatus() == 0 || r.getStatus() == 25) && isExpired(r.getCreateTime())) {
                r.setStatus(8);
            }
            if (r.getStatus() == 0 || r.getStatus() == 25) {
                //计算现在时间和订单时间差
                long orderTime = r.getCreateTime().plusHours(24).toInstant(ZoneOffset.of("+8")).toEpochMilli() - LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
                r.setOrderTime((int) orderTime);
            }
        });
        return new CheckOrderVO().setPage(list).setCount(payCount(orderDTO.getClinicId()));
    }

    private boolean isExpired(LocalDateTime createTime) {
        return LocalDateTime.now().isAfter(createTime.plusHours(24));
    }

    private Integer payCount(Long clinicId) {
        TestOrderQry qry = new TestOrderQry();
        qry.setPageSize(1);
        qry.setPageIndex(1);
        qry.setClinicId(clinicId);
        qry.setStartTime(LocalDateTime.now().minusHours(24));
        qry.setEndTime(LocalDateTime.now());
        //0：已检测待支付和 25：未检测待支付
        qry.setList(List.of(0, 25));
        return orderClient.pageOrderList(qry).getTotal();
    }

    public void cancelOrder(String orderNo) {
        var order = orderClient.getOne(new OrderQry().setTestOrderNo(orderNo));
        orderClient.update(order.getTestOrderId(), new TestOrderDTO().setStatus(8));
    }

    public void deleteOrder(String orderNo) {
        var order = orderClient.getOne(new OrderQry().setTestOrderNo(orderNo));
        orderClient.delete(order.getTestOrderId());
    }
}